#include "kvm-common/ipc-helper.h"

#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <thread>
#include <chrono>
#include <sys/un.h>
#include <sys/inotify.h>
#include <arpa/inet.h>
#include "logger/phlogger.h"
/**
 * @brief 循环使用get_packs,循环获取单个的数据包 常用于音频，会发送多个单独的数据包被一次性接收
 * 
 * @param buffhead buffer地址
 * @param bufflen buffer 长度
 * @param outbuff 输出地址
 * @param outlen 输出长度
 * @param outmax 最大输出数量
 * @param outmaxoffset 数据包扫描到的最远偏移
 * @return int 数据包个数
 */
int IPC_HELPER::get_packs_all(unsigned char *buffhead, int bufflen, void *outbuff[], unsigned int outlen[], int outmax,int *outmaxoffset)
{
	int outpacks = 0;
	int offset = 0;

	while (true) {
		int thisoffset = 0;
		int ret = get_packs(buffhead + offset, bufflen - offset, &outbuff[outpacks], &outlen[outpacks], outmax - outpacks, &thisoffset);
		if (ret <= 0) {
			if(outmaxoffset) {
				*outmaxoffset = offset;
			}
			return outpacks;
		}
		offset += thisoffset;
		outpacks += ret;
	}
	return -1;
}

/**
 * @brief dubug 显示二进制
 * 
 * @param inbuff 
 * @param num 
 */
void disp_x_buff(void *inbuff, int num)
{
	unsigned char *buff = (unsigned char *)inbuff;
	for (int i = 0; i < num; i++) {
		printf("%#02x ", buff[i]);
	}
	printf("\n");
}

/**
 * @brief crc16/modbus校验 
 * 
 * @param ptr 
 * @param len 
 * @param firstcrc 初始值一般使用 0xFFFF
 * @return unsigned int 
 */
unsigned int crc_make(unsigned char *ptr, int len, unsigned int firstcrc)
{
	unsigned int crc = firstcrc;
	unsigned char i;
	while (len != 0) {
		if (len < 0)
			len = 0;
		crc ^= *ptr;
		for (i = 0; i < 8; i++) {
			if ((crc & 0x0001) == 0)
				crc = crc >> 1;
			else {
				crc = crc >> 1;
				crc ^= 0xa001;
			}
		}
		len -= 1;
		ptr++;
	}
	return crc;
}

/**
 * @brief 从buffer中寻找数据包
 * 
 * @param buffhead buffer地址
 * @param bufflen buffer 长度
 * @param outbuff 输出地址
 * @param outlen 输出长度
 * @param outmax 最大输出数量
 * @param outmaxoffset 数据包扫描到的最远偏移
 * @param pack_aim_offset 数据包头告知的目标偏移
 * @return int 返回获取到的数据包
 */
int IPC_HELPER::get_packs(unsigned char *buffhead, int bufflen, void *outbuff[], size_t outlen[], int outmax, int *outmaxoffset, int *pack_aim_offset)
{
	int retpack = 0;
	if (!buffhead || bufflen <= 0 || !outbuff || outmax <= 0) {
		return -1;
	}
#if 0
	disp_x_buff(buffhead, 40);
#endif
	int offset = 0;
	do {
		//check head
		if ((buffhead[offset + 0] == 0xaa) && (buffhead[offset + 1] == 0xbb) && (buffhead[offset + 2] == 0xcc)) {
			//check len
			int getpack = buffhead[offset + 3];

			int retpack = getpack > outmax ? outmax : getpack;
			if (retpack <= 0){
				return -2;
			}				

			int datoffset = offset + 4 + getpack * 4;
			//crc check
			unsigned int calcrc = crc_make(buffhead + offset, datoffset - offset, 0xffff);
			//小端字节
			unsigned int rdcrc = buffhead[datoffset + 0] | buffhead[datoffset + 1] << 8;

			if (calcrc != rdcrc) {
				WriteLog(LOG_WARNING, "get crc error  %x,%x,buff@%d,len = %d", calcrc, rdcrc, offset, datoffset);
				continue;
			}

			int totallen = 0;
			int outpack = 0;
			//计算最大包长度
			int allpacklen = 0;
			for (int i = 0; i < retpack; i++) {
				int len;
				memcpy(&len, buffhead + offset + 4 + i * 4, 4);
				allpacklen += len;
			}
			if (pack_aim_offset)
				*pack_aim_offset = offset + 4 + getpack * 4 + 2 + allpacklen;

			if (offset != 0) {
				WriteLog(LOG_WARNING, "drop dat len = %d", offset);
#if 0
				disp_x_buff(buffhead,20);
#endif
			}

			for (int i = 0; i < retpack; i++) {
				int getlen = 0;
				int lenoffset = offset + 4 + i * 4;

				memcpy(&getlen, buffhead + lenoffset, 4);

				int buffoffset = offset + 4 + getpack * 4 + 2 + totallen;

				totallen += getlen;
				//剔除未接收到的数据
				if (buffoffset + getlen <= bufflen) {
					outbuff[outpack] = buffhead + buffoffset;
					outlen[outpack] = getlen;
					outpack++;
					if (outmaxoffset) {
						*outmaxoffset = buffoffset + getlen;
					}
				} else {
#if 0
					printf("buffoffset =%d,%d,%d,allpacklen = %d\n", buffoffset, getlen, bufflen, allpacklen);
#endif
					break;
				}
			}
			return outpack;
		}
	} while (offset++ < (bufflen - 4));
	return -3;
}

static std::string force_videoserver_ip; ///调试用ip
static int force_videoserver_port; ///调试用端口
static int force_video_flg = 0;

void IPC_HELPER::set_force_videoserver_ip(const char *ip)
{
	force_videoserver_ip = ip;
	force_video_flg |= 1 << 0;
}
void IPC_HELPER::set_force_videoserver_port(int port)
{
	force_videoserver_port = port;
	force_video_flg |= 1 << 1;
}

/**
 * @brief 尝试连接socket 
 * 
 * @param socketfd 
 * @return int 
 */
int IPC_HELPER::try_get_connected_fd(int &socketfd)
{
	if (socketfd <= 0) {
		int ret = -1;
		//先连接强制地址
		if (force_video_flg == 0x3) {
			WriteLog(LOG_WARNING, "connect force video server %s,%d", force_videoserver_ip.c_str(), force_videoserver_port);
			ret = try_get_connected_fd(socketfd, force_videoserver_ip.c_str(), force_videoserver_port);
			return ret;
		}

		//先连接uds端口
		ret = try_get_connected_fd(socketfd, deliver::UDS_NAME);
		if (ret > 0)
			return ret;
		//uds连接失败使用tcp端口
		ret = try_get_connected_fd(socketfd, "127.0.0.1", deliver::port);
		return ret;
	}
	return socketfd;
}

/**
 * @brief 连接tcp端口
 * 
 * @param socketfd 
 * @param ipaddr ip地址
 * @param port 端口
 * @return int 
 */
int IPC_HELPER::try_get_connected_fd(int &socketfd, const char *ipaddr, int port)
{
	if (socketfd <= 0) {
		socketfd = socket(AF_INET, SOCK_STREAM, 0);

		struct sockaddr_in serv_addr;
		serv_addr.sin_family = AF_INET; // IP V4
		serv_addr.sin_port = htons(port); // 端口
		serv_addr.sin_addr.s_addr = inet_addr(ipaddr);

		int ret = connect(socketfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

		if (ret != 0) {
			close(socketfd);
			socketfd = -1;
		} else {
			setnonblocking(socketfd);
		}
	}
	return socketfd;
}

/**
 * @brief 连接uds端口
 * 
 * @param socketfd 
 * @param paths 用于共享数据的地址
 * @return int 
 */
int IPC_HELPER::try_get_connected_fd(int &socketfd, const char *paths)
{
	if (socketfd <= 0) {
		socketfd = socket(AF_UNIX, SOCK_STREAM, 0);

		struct sockaddr_un serv_addr;
		memset(&serv_addr, 0, sizeof(serv_addr));

		serv_addr.sun_family = AF_UNIX; //uds
		strcpy(serv_addr.sun_path, paths);

		int ret = connect(socketfd, (const struct sockaddr *)&serv_addr, sizeof(serv_addr));

		if (ret != 0) {
			close(socketfd);
			socketfd = -1;
		} else {
			setnonblocking(socketfd);
		}
	}
	return socketfd;
}

/**
 * @brief 切换到指定的频道 
 * 
 * @param socketfd 
 * @param chn 1:video 2:audio 3:usb
 * @return int 
 */
int IPC_HELPER::change_to_channel_socket(int socketfd, int chn)
{
	char code[128];
	const char *point;
	switch (chn) {
	case 1:
		point = "video";
		break;
	case 2:
		point = "audio";
		break;
	case 3:
		point = "usb";
		break;
	default:
		return -2;
	}
	sprintf(code, "assign %s", point);
	WriteLog(LOG_INFO, "try assign %s channel", point);

	int wrlen = strlen(code);
	int wrret = write(socketfd, code, wrlen);
	if (wrret != wrlen) {
		return -1;
	}
	std::this_thread::sleep_for(std::chrono::milliseconds(20));
	return 0;
}

/**
 * @brief 简单的命令交互 内部启动一个socket 连接 交互后断开
 * 
 * @param sndcmd 发送的命令
 * @param retcmd 获取到的数据
 * @param retmaxlen 获取buff长度
 * @return int 
 */
int IPC_HELPER::simple_read_write_cmd(const char *sndcmd, char *retcmd, int retmaxlen)
{
	int socketfd = -1;
	int ret = -1;
	do {
		int sret = try_get_connected_fd(socketfd);
		if (sret < 0) {
			ret = -1;
			break;
		}

		const char *code = sndcmd;
		int wrlen = strlen(code);
		int wrret = write(socketfd, code, wrlen);
		if (wrret != wrlen) {
			ret = -2;
			break;
		}

		pollfd ipollfd;
		ipollfd.fd = socketfd;
		ipollfd.events = POLLIN;
		ipollfd.revents = 0;

		int pret = poll(&ipollfd, 1, 500);
		if (pret <= 0) {
			ret = -3;
			break;
		}

		ret = read(socketfd, retcmd, retmaxlen);
	} while (0);

	if (socketfd > 0) {
		close(socketfd);
		return ret;
	}
	printf("error ret = %d\n", ret);
	return ret;
}

/**
 * @brief 获取分辨率,dat仅使用5个 后续预留数据
 * 
 * @param dat 
 * @return int 
 */
int IPC_HELPER::getResolution(int dat[10])
{
	const char *code = "get resolution";
	int ret = simple_read_write_cmd(code, (char *)dat, sizeof(int) * 10);
	return ret > 0 ? 0 : ret;
}

int IPC_HELPER::set_image_quality(unsigned int frame_rate, unsigned int bit_rate)
{
	const char *cmd_code = "set image quality?frame_rate=%d&bit_rate=%d";
	char sendtmpbuf[128];
	char recvtmpbuf[128];
	sprintf(sendtmpbuf,cmd_code,frame_rate,bit_rate);
	return simple_read_write_cmd(sendtmpbuf, recvtmpbuf, sizeof(recvtmpbuf));
}

int IPC_HELPER::set_hdmi_status(int val)
{
	const char *getwd[] = {
		"get out status off",
		"get out status on",
	};
	const char *setwd[] = {
		"set out status off",
		"set out status on",
	};

	const char *code = setwd[!!val];
	char tmpbuf[128];
	int ret = simple_read_write_cmd(code, tmpbuf, sizeof(tmpbuf));

	for (int i = 0; i < 2; i++) {
		if (!strcmp(getwd[i], tmpbuf)) {
			return i;
		}
	}
	return -1;
}

int IPC_HELPER::get_hdmi_status(void)
{
	const char *getwd[] = {
		"get out status off",
		"get out status on",
	};

	const char *code = "get out status";
	char tmpbuf[128];
	int ret = simple_read_write_cmd(code, tmpbuf, sizeof(tmpbuf));

	for (int i = 0; i < 2; i++) {
		if (!strcmp(getwd[i], tmpbuf)) {
			return i;
		}
	}
	return -1;
}

/**
 * @brief 从socket获取数据
 * 
 * @param socketfd socket fd
 * @param buff 输入buffer
 * @param maxlen 输入最大长度
 * @param timout 超时时间
 * @return int 数据包长度
 */
int IPC_HELPER::get_datas(int socketfd, unsigned char *buff, int maxlen, int timout)
{
	pollfd ipollfd;
	ipollfd.fd = socketfd;
	ipollfd.events = POLLIN;
	ipollfd.revents = 0;

	int pollret = poll(&ipollfd, 1, timout);

	if (pollret <= 0) {
		return pollret - 1000;
	}

	int frame_len = 0;

	while (frame_len < maxlen) {
		int ret = read(socketfd, buff + frame_len, maxlen - frame_len);
		if (ret > 0) {
			frame_len += ret;
		} else if (errno == EAGAIN && ret == -1) {
			break;
		} else {
			if (frame_len > 0) {
				return frame_len;
			}
			return -errno;
		}
	}
	if (frame_len > 0) {
		return frame_len;
	}
	return -errno;
}

/**
 * @brief 设置未非阻塞模式
 * 
 * @param sock 
 */
void setnonblocking(int sock)
{
	int opts;
	opts = fcntl(sock, F_GETFL);
	if (opts < 0) {
		perror("fcntl(sock,GETFL)");
	}
	opts = opts | O_NONBLOCK;
	if (fcntl(sock, F_SETFL, opts) < 0) {
		perror("fcntl(sock,SETFL,opts)");
	}
}

/**
 * @brief Set the Watch File object 监控文件，并使用profun
 * 
 * @param fil 文件名
 * @param profun 处理函数
 * @param priv 处理函数的私有变量
 * @param runningflg 异步关闭用标志
 */
void SetWatchFile(const char *fil, std::function<void(void *priv, const char *fil)> profun, void *priv, std::shared_ptr<int> runningflg)
{
#define INF_BUF_LEN (1024)
	int fd = inotify_init1(IN_NONBLOCK);

	if (fd < 0) {
		return;
	}

	char buff[INF_BUF_LEN];
	sprintf(buff, "touch %s", fil);

	system(buff);

	int wd = inotify_add_watch(fd, fil, IN_MODIFY | IN_CREATE | IN_DELETE | IN_DELETE_SELF);

	if (wd < 0) {
		close(fd);
		return;
	}

	pollfd ipollfd;
	ipollfd.fd = fd;
	ipollfd.events = POLLIN;
	ipollfd.revents = 0;
	int len;
	while (runningflg && *runningflg) {
		if (0 >= poll(&ipollfd, 1, 100))
			continue;
		if (0 < (len = read(fd, buff, INF_BUF_LEN))) {
			inotify_event *ievent;
			for (char *ptr = buff; ptr < buff + len;
			     ptr += sizeof(inotify_event) + ievent->len) {
				ievent = (inotify_event *)(ptr);
#if 0
				fprintf(stdout, "len = %d ,mask = %d,watch fd = %d,len = %d", len, ievent->mask, ievent->wd, ievent->len);
				if (ievent->len) {
					fprintf(stdout, ",name= %s", ievent->name);
				}
				fprintf(stdout, "\n");
#endif
				if (profun)
					profun(priv, fil);
			}
		}
	}
}

/**
 * @brief 获取usb数据
 * 
 * @param outname 输入buffer,返回数据
 * @param sz buffer长度
 * @return int >0 表示buffer中数据长度 <0 表示错误
 */
int IPC_HELPER::get_usb_info(char *outname, int sz)
{
	const char *code = "get usb info";
	int ret = simple_read_write_cmd(code, outname, sz);
	return ret;
}

/**
 * @brief 获取I帧
 * 
 * @param socketfd  
 * @return int 返回状态 <=0 表示错误
 */
int IPC_HELPER::get_I_frame(int socketfd)
{
	const char *code = "get I frame";
	int clen = strlen(code);
	return write(socketfd, code, clen);
}

int IPC_HELPER::get_frame_data(int socket)
{
	const char *code = "get data frame";
	int clen = strlen(code);
	return write(socket, code, clen);
}

std::unique_ptr<SEND_PACK> IPC_HELPER::make_send_pack(void **frame_addr, size_t *frame_size, int pack_count)
{
	std::unique_ptr<SEND_PACK> ret(new SEND_PACK);

	ret->headbuff.resize(pack_count * 20 + 20);
	unsigned char *headbuff = &ret->headbuff[0];
	auto &bufvec = ret->bufvec;
	bufvec.resize(1);

	headbuff[0] = 0xaa;
	headbuff[1] = 0xbb;
	headbuff[2] = 0xcc;
	headbuff[3] = pack_count;

	//填充数据
	for (int i = 0; i < pack_count; i++) {
		*(int *)&headbuff[4 + 4 * i] = frame_size[i];
		iovec tmpiovec;
		tmpiovec.iov_base = frame_addr[i];
		tmpiovec.iov_len = frame_size[i];

		bufvec.push_back(tmpiovec);
	}

	bufvec[0].iov_base = headbuff;
	bufvec[0].iov_len = (pack_count + 1) * 4 + 2;

	//head部分添加crc
	unsigned int calcrc = crc_make(headbuff, (pack_count + 1) * 4, 0xffff);
	//小端字节填充
	headbuff[(pack_count + 1) * 4 + 0] = calcrc;
	headbuff[(pack_count + 1) * 4 + 1] = calcrc >> 8;
#if 0
	disp_x_buff(headbuff, (pack_count + 1) * 4 + 2);
#endif

	ret->aimlen = 0;
	for (auto &p : bufvec) {
		ret->aimlen += p.iov_len;
	}

	return ret;
}
